                         # 美多商城
        # 验证模块 verifications      #用户中心模块 users
        # 地域模块 areas              #第三方登录认证模块 oauth
        # 商品模塊 goods              # 購物車模塊 cart
        # 訂單模塊 order              # 支付模塊  pay
        # 詳情模塊 contents
########################---開發思想---#################################
""" # 400 請求不被允許
    # 403 禁止訪問
    # 404 路由设置有问题
    # 405 方法不被允许
    # 500 代码哪里有问题，加断点 + dubug模式
1. 先分析总体页面的需求
2. 再分析具体的某一个小需求

需求：
    把需求（用户做了什么，要前端传递什么，我们后端要干什么）写下来

    用户在输入框中输入了用户名，前端需要将用户名提交给后端，后端需要验证用户名是否重复

后端的思路：
    需求：
        判断用户名是否重复 可以返回给前端一个 标记（true/false）
                        也可以返回一个 count   count:0     没有注册过
                                            count：1     有注册过
    1. 确定前端需要传递什么数据给后端
        把一些简要的步骤写下来
        ① 查询数据库
        ② 返回一个count
    2. 确定请求方式 和 路由
        GET         users/usernames/itcast/
                    users/usernames/(?P<username>\w{5,20})/
                    users/usernames/(?P<username>\w{5,20})/count/
        POST
        PUT
        DELETE

    3.确定使用哪个视图
        APIView                     ：  DRF的基类
        GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
        ListAPIVIew,RetrieveAPIView :  连get方法都不用写，自动调用系统的mixin实现增删改查

    4.按照我们的需求分析，实现步骤开发就可以

########################---多表拆分---##############################

1. 根据ui图或者原型 尽量多的分析字段 （比较明显的表可以定义出来，暂时不要分析表和表之间的关系）
2. 找一个安静的时间，没有人打扰你的地方进行表和表之间的分析（就是分析 2个表之间的关系）

只要是多对多就是拆分成 3个表

学生id    学生name
 1          lilei
 2          hanmeimei

老师id    老师name
 666        laowang
 999        zhangsan


第三个表
学生id    老师id
1           666
1           999
2           666
2           999

"""
########################---mysql數據備份---##############################
"""
    mysql -uroot -p 新数据库名 < 备份数据
    mysqlump -uroot -p 备份数据 > 要备份的新数据库名
    source stock_db.sql 导入数据库（需要在有备份数据的地方进入）
"""
########################---(script (SKU)(elasticsearch)---> ..html.py)---##############################
"""
    功能：手动生成所有SKU的静态detail html文件
    使用方法:
        ./regenerate_detail_html.py
"""
"""
########################---(script (SKU)---> elasticsearch)---##############################
1. 搜索引擎是 我们需要组织数据，把数据组织好，组织好之后给搜索引擎，让搜索引擎帮助我们进行分词处理


数据 --> haystack (需要在虚拟环境中执行 rebuild_index) --> elasticsearch

① 数据
    我们组织号了数据之后，需要给 elasticsearch
    我们不用自己写代码，我们可以借助于 haystack 来帮助我们完成 数据传递给elasticsearch

② 需要有搜索引擎 （elasticsearch） 通过docker来搭建

"""
########################@@@---跨域CORS---@@@#################################
"""
    跨域CORS
        编辑/etc/hosts文件，可以设置本地域名

        sudo vim /etc/hosts
        在文件中增加两条信息

        127.0.0.1   api.meiduo.site
        127.0.0.1   www.meiduo.site

    在前端front/js目录中，创建host.js文件用以为前端保存后端域名

    var host = 'http://api.meiduo.site:8000';

    安装
        pip install django-cors-headers

    添加应用(注册子应用)

        INSTALLED_APPS = (
            'corsheaders',
        )

    设置中间层

    MIDDLEWARE = [

        'corsheaders.middleware.CorsMiddleware',
    ]

    添加白名单

    # CORS
    CORS_ORIGIN_WHITELIST = (
        '127.0.0.1:8080',
        'localhost:8080',
        'www.meiduo.site:8080'
    )
    CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie

    设置允许访问的主机地址
    ALLOWED_HOSTS = ['127.0.0.1','api.meiduo.site']

"""
########################@@@---異步任務(Celery)---@@@#################################
"""
    #我们需要指令让Celery单独执行

# 异步任务（一）email --> tasls.py
在celerytasks目录中新建email目录和`email/_init.py文件和email/tasks.py`文件
在email/tasks.py文件中是实现发送邮件的异步任务
from celery_tasks.main import app
from django.core.mail import send_mail
from mall import settings
@app.task(name='send_verify_mail')
def send_verify_mail(to_email,verify_url):
    subject = "美多商城邮箱验证"
    html_message = '<p>尊敬的用户您好！</p>' \
                   '<p>感谢您使用美多商城。</p>' \
                   '<p>您的邮箱为：%s 。请点击此链接激活您的邮箱：</p>' \
                   '<p><a href="%s">%s<a></p>' % (to_email, verify_url, verify_url)
    send_mail(subject, "", settings.EMAIL_FROM, [to_email], html_message=html_message)



# 异步任务（二）sms --> tasks.py
from celery_tasks.main import app
from libs.yuntongxun.sms import CCP


@app.task(name='send_sms_code')
def send_sms_code(mobile,sms_code):
    ccp = CCP()
    ccp.send_template_sms(mobile,[sms_code, 5],1)


# config.py
broker_url  = 'redis://127.0.0.1/14'
result_backend  = 'redis://127.0.0.1/15'

# main.py   添加任务(Celery)
from celery import Celery
#进行Celery允许配置
# 为celery使用django配置文件进行设置
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings'
#创建Celery对象
#参数main 设置脚本名
app = Celery('celery_tasks')
#加载配置文件
app.config_from_object('celery_tasks.config')
#自动加载任务
app.autodiscover_tasks(['celery_tasks.sms','celery_tasks.mail','celery_tasks.html'])

#需要在虚拟环境中执行   （worker）
# celery -A celery对象的路径 worker -l info
# celery -A celery_tasks.main worker -l info

发送短信\郵件\靜態頁面 是耗时操作 使用 celery 进行异步执行
Celery异步执行通过delay()方法将手机号 短信验证码内容给celery进行异步执行
Celery-->单独的任务模块
Celery_tasks中创建sms包里面tasks.py执行发送短信使用云通讯的实例CCP()进行短信发送 此方法必须被celery的实例的task装饰-->@app.task()
Celery的config中配置celery的中间人broker再redis中的位置和执行结果再redis中的位置
Celery的main中 加载系统配置文件 创建celery实例 加载配置问价 使用celery实例对象自动检测tasks文件 的任务

创建worker  celery -A celery_tasks worker -l info

"""
########################@@@---使用Docker安裝FastDFS分布式文件系统---@@@#################################
"""
#修改 ip 地址： sudo ifconfig ens33 192.168.31.142

开启tracker 服務

docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker

开启storage 服務

docker run -dti --network=host --name storage -e TRACKER_SERVER=192.168.31.142:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage

停止/開啓 storage 服務

docker container stop/start storage

docker container rm 容器名或容器id  # 刪除 容器

docker container ls   # 查看正在運行的服務

"""
########################@@@---Docker---@@@#################################
"""
Docker

1.Docker的架构是 C/S模式      客户端/服务器
2.Docker的镜像和容器

    镜像： 可以暂时理解为 安装系统的镜像
          还可以理解为  虚拟环境
     docker pull 镜像名            从远程服务器 把镜像 下载下来
     docker load -i 本机镜像路径        可以从本地加载
     docker save -o 保存名字.rar  镜像名
     docker images                  查看有那些镜像
     docker image rm 镜像名        删除镜像

    容器： 可以理解为 运行起来的 镜像（容器运行需要依赖于 镜像）
          可以依赖于一个镜像 创建很多个容器

        docker run [选项] 镜像名 [指令]
                    --name 名字  给容器起名字
                    -i          交互模式
                    -d          守护进程
                    -t          给它一个终端

        docker run -it ubuntu       -i 交互模式： 退出之后，容器就关闭了
        docker run -it ubuntu /bin/bash

        docker run -d  ubuntu       -d 守护进程： 在后台运行

        docker container ls --all       罗列所有的容器（包括在运行和未运行的）
        docker container ls             罗列在运行的容器

        docker container start/stop  容器id/容器名

        docker exec [选项] 容器名/容器id   命令          进入到正在运行的容器
        docker exec -it   容器id/容器名  /bin/bash

        docker container rm 容器id/容器名        只能删除已经停止的容器，
                                                强制删除需要加 -f
##########################################---docker---############################
        docker load -i 壓縮包  安裝 壓縮鏡像

        docker commit -m 'meiduo' myubuntu myubuntu:v1.2   創建新的鏡像+動作
        docker commit  myubuntu myubuntu:v1.3   創建新的鏡像

        docker build -t nginx:v2 . 當前路徑創建鏡像

        docker run -dit -p 192.168.31.142:10086:80 nginx:v2  國定ip及端口

        curl ip  顯示 內容

        docker rmi [image_id/image_name:image_version]  刪除鏡像

        docker ps -a  顯示所有的進程
        docker inspect [容器id] 詳細信息
        docker run --name container myubuntu ubuntu    重命名
        docker exec -it docker容器號 /bin/bash   進入到容器
        docker run -dit --name myubuntu -v /home/python/Desktop/38_01 ubuntu
        docker create -v /home/python/Desktop/abc --name vc-share ubuntu 創建共享目錄地址
        docker run --volumes-from vc-share -dit --name test1 ubuntu
        docker run --volumes-from vc-share -dit --name test2 ubuntu



        开启 tracker 服務

        docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker

        开启 storage 服務

        docker run -dti --network=host --name storage -e TRACKER_SERVER=192.168.31.142:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage

        停止/開啓 storage 服務

        docker container stop/start storage

        docker container rm 容器名或容器id  # 刪除 容器
        docker  rm  -f 容器名或容器id  # 刪除 容器

        创建 elasticsearch 容器

        docker run -dti --network=host --name=elasticsearch -v /home/python/Downloads/docker/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

        docker container start tracker
        docker container start storage
        docker container start elasticsearch


        FROM python:3.5

        LABEL maintainer="xmg@itcast.cn"
        LABEL version="1.0"

        RUN pip install django==1.11.11

        WORKDIR /code

        COPY ./bookmanager ./

        EXPOSE 8000

        CMD ["python3","manage.py","runserver","0.0.0.0:8000"]

"""
########################@@@---自定义存储系统---@@@#################################
"""
1.您的自定义存储系统必须是以下的子类 django.core.files.storage.Storage：
2.Django必须能够在没有任何参数的情况下实例化您的存储系统。
    这意味着任何设置都应该来自django.conf.settings
3.您的存储类必须实现_open()和_save() 方法以及适用于您的存储类的任何其他方法
4.您的存储类必须是可解构的， 以便在迁移中的字段上使用时可以对其进行序列化。
只要您的字段具有可自行序列化的参数，就 可以使用
django.utils.deconstruct.deconstructible类装饰器
（这就是Django在FileSystemStorage上使用的）

富文本编辑器 后台编辑前端展示样式的工具

"""
########################@@@---序列化器的功能---@@@#################################
"""
序列化器的功能：
1. 验证数据
2. 数据入库
3. 将模型转换为字典

# serializers.ModelSerializer
    # 注意： ModelSerializer 我们自己添加的字段，也是需要 添加到fields中
            # 如果不添加会报错
#   自动生成字段 （根据fileds来生成字段）

    1.数据类型的验证
    2.数据选项的验证
    3.单个字段验证
    4.多个字段验证

    # read_only 只读 只进行序列化操作
    # write_only 只是写入，在当前情况下 值用于 反序列化的数据验证操作，
    # 进行序列化操作的时候默认忽略此字段
    # sms_code,password2,allow 只是使用它来进行 数据的验证 反序列化

# 当父类的需求 不能满足我们的开发的时候 就是重写
    # 将数据保存在redis
        #1. 连接redis
"""
########################@@@---登陸驗證邏輯模塊---@@@#################################
##############################---圖片驗證（verifications）---#############################
"""
需求：
    前端生成一个uuid，前端把生成的uuid给后端，后端接收，并且生成一个图片验证码

后端：
    一，大体的思路
    1.接收uuid
    2.生成一个图片验证码
    3.保存验证码内容
    4.把图片返回回去

    二,确定请求方式和路由
    GET    /verifications/imagecodes/(?P<image_code_id>.+)/

    三 确定视图
    APIView                     ：  DRF的基类
    GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
    ListAPIVIew,RetrieveAPIView :  连get方法都不用写，自动调用系统的mixin实现增删改查

    四 按照步骤开发

"""
##############################---短信驗證（verifications）---#############################
"""
需求：
    当用户输入手机号，验证码之后，需要让前端将 手机号，图片验证码内容以及UUID 传递给后端

后端：
    大体步骤：
    1.接收数据
    2.校验数据
        验证手机号
        验证图片验证码是否与redis的一致
    3.生成一个短信验证码
    4.云通讯发送
    5.保存短信验证码到redis中
    6.返回相应

    确定请求方式和路由
    GET
        verifications/(?P<mobile>)/(?P<image_code_id>)/(?P<text>)/

        verifications/?mobile=xxx&image_code_id=xxx&text=xxx

        verifications/(?P<mobile>1[3-9]\d{9})/?image_code_id=xxx&text=xxx


    确定视图
        APIView                     ：  DRF的基类
        GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
        ListAPIVIew,RetrieveAPIView :  连get方法都不用写，自动调用系统的mixin实现增删改查

    图片验证使用到captcha 第三方模块 放在第三库文件夹libs中
    使用captcha的generate_captcha()方法生成图片验证码保存在redis中
    接收前端传入的uuid通过这个uuid从reids中查询出相应的图片
    验证码的内容
    再将这个图片验证码返回给前端

    短信验证码使用云通讯进行短信的发送 放在第三库文件夹libs中
    接收前端传过来的用户名 密码 手机号 图片验证码 短信验证码 使用序列化器进行验证 保存
    通过手机号 从redis中获取真正的短信验证码进行匹配
##############################---耗時操作（celery）---#############################
    发送短信 耗时操作 使用 celery 进行异步执行
    Celery异步执行通过delay()方法将手机号 短信验证码内容给celery进行异步执行
    Celery-->单独的任务模块
    Celery_tasks中创建sms包里面tasks.py执行发送短信使用云通讯的实例CCP()进行短信发送 此方法必须被celery的实例的task装饰-->@app.task()
    Celery的config中配置celery的中间人broker再redis中的位置和执行结果再redis中的位置
    Celery的main中 加载系统配置文件 创建celery实例 加载配置问价 使用celery实例对象自动检测tasks文件 的任务
    创建worker  celery -A celery_tasks worker -l info
"""
########################@@@---第三方登陸（oauth）---@@@#############################
"""

实现第三方登陆的步骤：
第一步： 先到相应的开发平台中，申请成为开发者
            个人开发者
            企业开发者
第二步： 成为开发者之后，申请应用
            appid
            appsecret
第三步： 按照文档开发就可以

    1.添加图标
    2.添加图标的按钮点击
    3.点击图标的时候调转到一个页面
    4.这个页面需要用户同意，同意之后获取一个code
    5.通过code换取token
    6.通过token 换取 openid

        openid是此网站上唯一对应用户身份的标识，
        网站可将此ID进行存储便于用户下次登录时辨识其身份，
        或将其与用户在网站上的原有账号进行绑定。
        openid 要和 已有账号进行一个 1:1的关系

        openid      user
        abc         1
                                第三方网站 保存这个就可以
    QQ  12345678        -->      abcdefg

    OAuth2.0
    对于应用而言，需要进行两步：
        1. 获取Authorization Code；
        2. 通过Authorization Code获取Access Token

我们后端 需要拼接 跳转的url，拼接成功之后，返回给前端

看接口文档

当用户同意登陆之后， 腾讯的服务器会生成一个code， 这个code在回调的url中
我们需要让前端将 code 传递给后端

我们后端需要 通过code 换取token

一，大体的步骤
    1. 接收code
    2. 通过code换取token （是我们通过 请求 向腾讯的服务器换）
    3. 通过token换取openid (openid是此网站上唯一对应用户身份的标识)
二，确定请求方式和路由
    GET      /oauth/qq/users/?code=xxx
三，确定使用哪个视图

当用户点击头像授权同意的时候，后端会将 生成的code换取成最终的openid，然后返回给前端

当用户点击保存按照的时候，需要让前端将 手机号，密码，短信验证码以及 加密之后的openid提交给后端

后端：
    大体思路：
    1.接收参数 mobile,password,sms_code,access_token
    2.验证数据
        access_token
        sms_code
        根据手机号判断用户是否注册过

    3.保存数据
    4.返回相应

    请求方式和路由
        POST

    QQ第三方登录
    创建视图访问qq的登录页面
    用户登录点击统一 qq服务器返回一个code给前端
    前端将将在这个code传给后端
    后端通过这个code使用OAUTHQQ的实例对象到qq获取access_token
    通过这个access_token使用OAUTHQQ的实例对象到qq获取openid
    通过这个查询数据库看账号是否存在
    存在将poenid与美多用户账户绑定
    不存在就创建新用户
    将openid与美多用户账户绑定
    返回记住用户登录状态的token
    使用itsdangerous的TimeJSONWebSignatureSerializer可以实现带有有效期的token 对token进行签名(加密)处理
    Generate_save_user_token(openid)
    即可实现qq账号登录

"""
##############################---手機號登陸（）---#############################
"""
产品的需求是：  用户不仅可以通过用户实现登陆，也可以通过手机号实现登陆

问题： 我们的登陆是采用的 JWT，  JWT的认证也是使用系统的认证，先认证成功之后，才会生成token

结合我们的问题和需求：
1.我们需要重写 认证后端
2.我们需要在认证后端中，判断用户输入的是手机号还是用户名

JSON WEB TOKEN
通过这个token进行用户的登录状态保持
前端可以将这个token进行保存 每次访问的时候再headers中携带这个token 后端通过这个token判断用户是否登录

抽取（封装）
0.目的
    解耦

1.原则：
    ① 部分代码实现了一个小功能
    ② 重复性的代码（第二次出现的代码）

2.步骤：
    ① 定义一个方法，将要抽取的代码复制到这个方法中（抽取的代码注释，不要删除）
    ② 复制的代码 ，哪里有问题改哪里，缺少的变量以参数的形式定义
    ③ 验证，验证成功之后，再将抽取的代码删除
"""
########################@@@---個人中心業務邏輯模塊(users)---@@@#############################
##############################---用戶模型類分析（models）---#############################
"""
    分析出来： 要保存 用户名，密码，手机号 之后
    1. 可以自己定义模型
    2.  django 自带系统认证
        经过分析发现，系统的模型类，少一个字段，我们可以通过继承的方式来添加一个字段
        from django.contrib.auth.models import AbstractUser

        因为我们现在已经定义了自己的User，我们需要让系统使用我们自己定义的模型类，
        需要进行一个配置信息的设置

        我们是需要替换User
        通过提供一个值给AUTH_USER_MODEL设置，指向自定义的模型，Django允许你覆盖默认的User模型：

        AUTH_USER_MODEL = 'myapp.MyUser'
"""
##############################---注冊實現（）---#################################
"""
    用户名的重复验证
    接收前端传入的username
    从数据库中查询该字段的count 为1则重复 为0则可以使用
    组织数据 返回数据

需求： （用户做了什么，前端应该做什么，后端做什么）
    需要把所有的信息都填写完成， 前端需要收集 用户名，手机号，密码，短信验证码，确认密码和是否同意协议
    后端完成注册

后端：
    一。大体的步骤
    1.接收数据（用户名，手机号，密码，短信验证码，确认密码和是否同意协议）
    2.验证数据
    3.数据入库
        # 当我们调用序列化器的save方法的时候，序列化器会调用 create方法
        重写create()方法
        删除不需要保存到数据的字段 password2 sms_code allow
        使用系统的密码加密方法set_password 将密码转换成密文
        返回用户保持登录状态的token

    4.返回相应
        # 在进行序列化
        #序列化 --> 将模型转换为字典
        # 将模型转换为字典，序列化器是如何实现的？
        # 序列化器就是通过 自己的字段 来获取 对应的模型的属性的数据
    二。确定请求方式和路由
    POST    /users/
    三。确定视图
        APIView                     ：  DRF的基类
        GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
        CreateAPIView                :  连post方法都不用写，自动调用系统的mixin实现增删改查
    四。按照步骤开发
      四，序列化器
        注冊序列化器的功能:
        1,驗證數據
        2,數據入庫
        3,講數據模型轉換爲字典

    注册需求：
        如果保存不了 数据，安装
    pip install django-cors-headers

    #  注册完成之后实现自动登陆
    #  注册完成之后
    #   什么是自动登陆 （ 注册完成之后，给它一个 token）

"""
##############################---個人中心---#############################
"""

    JSON WEB TOKEN
    通过这个token进行用户的额登录状态保持
    前端可以将这个token进行保存 每次访问的时候再headers中携带这个token 后端通过这个token判断用户是否登录
    需求： 必须是登陆用户才可以显示个人中心，而且是 需要让前端将用户信息传递过来的

认证方式采用的JWT
我们让前端把token传递过来
后端：
    一，大体的步骤
        # 这个接口 只能是登陆用户才能访问
    1.接收用户信息
        # 在这里 结合用户权限 我们就获取到了 用户信息
    2.根据用户信息进行查询（user）
    3.将user对象转换为字典，返回
    二，确定请求方式
    GET     /users/infos/

    三，选择视图
        APIView                     ：  DRF的基类
        GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
        ListAPIView,RetrieveAPIView  :  连get方法都不用写，自动调用系统的mixin实现增删改查
    四。按照步骤开发

"""
##############################---邮箱的 逻辑---#############################
"""
    保存用户邮箱信息
    重写序列化器的update()方法增加发送邮件

#####################----更新邮箱---#############################
一，更新邮箱的逻辑是：
    1.让前端将邮箱以及token传递给后端
    2.后端接收数据
    3.更新指定用户的信息
    4.返回相应
二，请求的路由和方式
    PUT     /users/emails/          put传递的数据在 请求的body中

三，选择视图
    APIView                     ：  DRF的基类
    GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
    UpdateAPIView                 :  连put方法都不用写，自动调用系统的mixin实现增删改查
四。按照步骤开发

#####################----激活邮箱---#############################
JSON WEB TOKEN
通过这个token进行用户的登录状态保持
前端可以将这个token进行保存 每次访问的时候再headers中携带这个token 后端通过这个token判断用户是否登录

当用户输入邮箱之后，点击保存，这个时候，前端需要将 邮箱发送给后端

后端：
    1.接收邮箱信息
    2.保存邮箱信息到数据库中
    3.发送一封激活邮件
    4.用户点击激活邮件的时候，我们需要修改邮件的标记位 ，由未激活变为已激活

需求：
    当用户点击激活连接的时候，会跳转到一个激活页面，这个激活页面需要让前端将token传递给后端

后端：
    一。大体步骤写下来
        1.接收token
        2.验证token
        3.根据验证之后的数据进行查询
        4.修改用户的激活状态
        5.返回相应

    二。请求方式和路由
        GET     users/email/verifications/?token=xxxx

    三。确定视图

    四。按照步骤开发
#####################----加密token（generic_verify_url(user_id,email)）---#############################
#1.创建序列化器
#2.组织数据
#3.进行加密处理
#4. 返回數據

#####################----解密token（check_active_token(token)）---#############################
#1.创建序列化器
#2.对数据解密， 解密的时候可能存在异常（進行異常捕獲）
#3.把数据返回回去




"""
##############################---地址管理()---#############################
"""


新增地址：
    当用户将新增地址数据，填写完成之后，需要让前端收集数据
    我们可以让前端将 用户id传递过来，但是我们讲的时候，让前端将token传递过来

    用户token，收件人，地址，电话，省，市，区，固定电话，邮箱发送给后端

后端：
    1.接收数据
    2.验证数据
    3.数据入库
    4.返回相应

    POST    /users/addresses/

    CreateAPIView

"""
########################@@@---地域業務邏輯模塊(areas)---@@@#############################
"""
用户中心的收获地址的增删改查
    省市区的地址查询
    省份信息 的parent=None 查询出相应的数据返回给前端
    否则就查询出子行政单位的数据返回给前端
    省级行政单位的数据的查询 单独一个序列化器
    子行政单位使用一个序列化器
    省市区的查询数据量比较大且频繁 就使用缓存进行数据库的优化(性能的提升)
    CacheResponseMinxi是rest_framework_extensions.cache.mixins的
    让省市区数据查询的视图集继承自CacheResponseMinxi,再进行相应的配置即可实现

    接收数据 查询数据库 验证数据 返回响应
    显示用户的所有收货地址
    接收数据 验证数据 返回响应的数据
    删除收货地址 使用的是逻辑的删除
    即 改变is_deleted字段的值为true
    设置默认地址
    接收前端传入的地址信息
    修改default_address字段的值为前端传入address
"""
##############################---最近浏览---#############################
"""
1. 当登陆用户访问商品详情的时候，需要让前端发送一个请求，将商品id和用户信息发送给后端

2. 当登陆用户访问个人中心的时候，需要获取当前用户的浏览记录

1. 当登陆用户访问商品详情的时候，需要让前端发送一个请求，将商品id和用户信息发送给后端
    用户信息让前端以 token的形式传递过来

    sku_id,user_id
    保存在 redis中

    1: [2,3,4,5,6]

    Redis


    String 字符串:     key:value

    List 列表:        key: [value1,value2,value2]     1:[2,3,4,5]

    Hash 哈希:        key:  field:value
                            field2:value2
                            field3:value3

    Set 集合:         key: [value2,value1,value3]

    ZSet 有序集合:     key: [value1,value2,value3]     1:[2,3,4,5]

后端
    接口只能是登陆用户访问
    大体步骤：
        接收数据
        验证数据
        把数据保存起来
        返回相应
    请求方式和路由
        POST    users/histories/

    确定视图
      APIView                     ：  DRF的基类
      GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
      CreateAPIView                 :  连post方法都不用写，自动调用系统的mixin实现增删改查

    按照步骤开发

2. 当登陆用户访问个人中心的时候，需要获取当前用户的浏览记录
    用户信息让前端以 token的形式传递过来

    后端：
        大体步骤：
            接收用户的信息
            根据用户的信息查询redis中的数据  [1,2,,3]
            根据id查询商品的信息             [sku，sku,sku]
            将对象列表转换为字典列表
            返回相应
        请求方式和路由
            GET
"""
########################@@@---首頁業務邏輯模塊(goods)---@@@#############################
##############################---首頁---#############################
"""
1.  分析首页需要进行功能的拆分 来获取数据
    分类数据在其他页面中都有展示
    首页商品信息 在商品表中
    这个时候 我们可以 分为2个接口来实现数据的获取
    一个接口就是分类数据
    另外一个接口就是 首页商品数据

2.  我们的首页访问量非常大

     会导致 我们的分类接口和首页商品数据接口 频繁访问
     频繁访问就直接导致 数据库经常查询这2类数据


3.  我们优化 这2个接口 因为数据不经常发生变化 ，所以我们可以采用缓存的处理

    很好的解决了 数据库频繁访问的问题
    但是 用户的体验问题没有解决（因为页面需要动态的生成， 需要时间）
       SEO  网站优化
            主要是考虑网站的搜索优化
            搜索网站主要是 爬去 静态页面 动态页面是抓取不到的
4. 我们需要对我们是首页做静态化处理

    静态化页面： 我们让用户去访问我们提前准备好的html


静态化页面： 我们让用户去访问我们提前准备好的html页面，这个html页面
            应该是从数据库中查询出来的最新的数据

            ① 先查询数据
            ② 将查询的数据 填充到 html中
            ③ 我们把最新的html文件放到指定的目录中


generate_static_index_html 生成静态化的方法 该什么时候去执行呢？
让系统去每间隔一段时间去做这个事情 -- 定时任务

##############################---定時任務---#############################


##############################---搜索引擎---#############################
    1. 搜索引擎是 我们需要组织数据，把数据组织好，组织好之后给搜索引擎，让搜索引擎帮助我们进行分词处理


    数据 --> haystack (需要在虚拟环境中执行 rebuild_index) --> elasticsearch

    ① 数据
        我们组织号了数据之后，需要给 elasticsearch
        我们不用自己写代码，我们可以借助于 haystack 来帮助我们完成 数据传递给elasticsearch

    ② 需要有搜索引擎 （elasticsearch） 通过docker来搭建

"""
########################---shell腳本語言(script ---> import_data.sh)---##############################
"""
    # import_goods_to_db.sh 執行文件
    #/bin/bash
    mysql -uroot -pmysql  meiduo_38 < goods_data.sql  # 測試數據

    chmod +x import_goods_to_db.sh  修改執行權限
    执行如下命令导入数据
    ./import_goods_to_db.sh

"""
########################---添加FastDFS保存的测试图片数据---##############################
"""

将/var/fdfs/storage中的data目录删除，将data.tar.gz文件拷贝到/var/fdfs/storage中，并解压缩

#解压测试文件压缩包
tar -zxvf data.tar.gz

#删除 storage原有的data文件
sudo rm -r /var/fdfs/storage/data

#移动或复制文件到 /var/fdfs/storage中
sudo mv data /var/fdfs/storage/
"""
########################---定时任务---##############################
"""
添加应用
INSTALLED_APPS = [
    'django_crontab',  # 定时任务
]
# 定时任务
CRONJOBS = [
    # 每5分钟执行一次生成主页静态文件
    ('*/5 * * * *', 'contents.crons.generate_static_index_html', '>> /home/python/Desktop/meiduo/mall/logs/crontab.log')
]
开启定时任务
添加定时任务到系统中

python manage.py crontab add
显示已经激活的定时任务

python manage.py crontab show
移除定时任务

python manage.py crontab remove
"""
##############################---列表頁面---#############################
"""
    列表数据的获取 我们发现有很多功能，有排序，有分页
    这个时候，我们一个一个去实现

    我们把这个分页类 抽取出去，让所有的分页的视图都采用这个分页类

    分页的视图 哪些是 分页的视图？
        APIView 没有分页
        GenericAPIView 及其它的子类有分页功能

    列表页面的需求分析：
        1. 需要让前端传递一个分类id
        2. 热销数据和列表数据的展示 我们分为2个接口

##############################---熱銷數據---#############################
热销数据的分析：
    一， 把大体的步骤写下来
        ① 后端接收分类id
        ② 根据分类id进行数据查询，并且进行排序操作  [SKU,SKU,SKU]
        ③ 将对象列表转换为 字典列表
        ④ 返回相应

    二，确定请求方式和路由
        GET  /goods/categories/(?P<category_id>\d+)/hotskus/

    三，确定使用哪个视图
         APIView                     ：  DRF的基类
        GeneriAPIView               ： 对列表视图和详情视图做了通用支持，一般和Mixin配合使用
        ListAPIView/RetrieveAPIView  :  连get方法都不用写，自动调用系统的mixin实现增删改查

##############################---列表數據---#############################
    1.列表数据 我们需要做静态化，因为分类数据基本很少发生变化，当数据发生变化的时候，
    我们可以重新生成静态化页面

    2.列表页面要做静态化， 只是对分类做静态化，那 列表数据/热销数据该如何获取呢？
       通过ajax 实现局部刷新


    静态化页面： 我们让用户去访问我们提前准备好的html页面，这个html页面
            应该是从数据库中查询出来的最新的数据

            ① 先查询数据
            ② 将查询的数据 填充到 html中
            ③ 我们把最新的html文件放到指定的目录中

"""
########################@@@---購物車業務邏輯模塊(cart)---@@@#############################
"""
#######################################---一邏輯分析---#############################

    1. 我们没有登陆也可以将商品放入购物车   购物车的数据是放在客户端
                                        放在cookie中

        登陆肯定可以将商品放入购物车      购物车的数据是保存在服务器
                                    mysql
                                    redis(为了使用redis的知识，)

    2. 没有登陆要保存的信息是：         商品id，商品个数，选中状态
        登陆要保存的信息是：  用户的id   商品id，商品个数，选中状态

    3.  没有登陆要保存的信息是 组织数据结构
            cart = {
                 sku_id:  {count:5,selected:True},
                 sku_id:  {count:3,selected:False},
            }

        登陆要保存的信息 :保存在redis中
            redis的数据是保存在内存中的，我们检查的原则是： 尽量少的占用内存空间

##############################---二、邏輯實現 # 添加到购物车---#############################

    当用户点击添加购物车按钮的时候，
    登陆用户需要将token sku_id,count,选中的状态提交给后端
    未登陆用户需要将    sku_id,count,选中的状态提交给后端


    1.后端接收数据（sku_id,count,selected）
    2.验证数据（验证商品id是否有对应的商品，商品的个数）
    3.获取数据
    4.得到用户的信息
    5.根据用户的信息进行判断
    # is_authenticated 认证用户
        # user 不能为 none 而且必须是认证用户
        6.登陆用户保存在redis中
            6.1  连接redis
            6.2  保存数据 hash  set
            6.3  返回相应
        7.未登录用户保存在cookie中
            7.1  先读取cookie信息
            7.2  判断是否有 购物车信息
                如果有则是加密的数据
                如果没有,则定义一个空字典
            7.3 判断这个商品是否在cookie中
                如果在则 累加数据
                如果不在则 直接添加
            cart = {1:{'count':5,'selected':True}}
            7.4 将购物车数据进行加密处理
                # 7.4.1 将字典转换为 bytes类型
                # 7.4.2 对bytes类型进行编码
            7.5 返回相应


##############################---三、登陸時 獲取購物車信息---#############################
    1.获取用户信息

    2.根据用户信息进行判断

    3.登陆用户从redis中获取数据
        3.1 连接redis
        3.2 获取hash ,set
        3.3 都获取商品id，count，selected
            因为redis获取的数据格式 和 cookie获取的数据格式不一致
            所以 根据id去获取商品的详细信息的业务逻辑不能统一

            我们就选择让数据 统一就行了

            为什么选择将 redis数据转换为 cookie格式
            redis但是数据获取之后都是 bytes

            # cookie_cart = {sku_id:{count:xxx,selected:True}}
    4.未登录用户从cookie中获取数据
        4.1 获取cookie数据
        4.2 判断cart数据
            如果有则进行解密
            如果没有就是空
        4.3.都获取商品id，count，selected

    5.根据id去获取商品的详细信息 sku

    6.将对象转换为字典


##############################---四、更改商品數量---#############################
    前端需要将 token(可选) ,sku_id(必须),count(必须),selected(必须) 提交给后端

    1.接收数据
    2.验证数据
    3.获取数据
    4.获取用户信息
    5.根据用户的状态进行判断
    6.登陆用户更新redis
        6.1 连接redis
        6.2 更新hash ,set
        6.3 返回数据
    7.未登录用户更新cookie
        7.1 先获取cart数据
        7.2 判断cart数据
            如果有则解密
            如果没有则初始化
        7.3 更新数据

        7.4 对数据进行加密
        7.5 返回数据

##############################---五、刪除商品---#############################
    当用户点击某一个商品的删除按钮的时候,需要让前端将sku_id(必须) 和 token(可选) 传递给后端
    1.接收数据
    2.验证数据
    3.获取数据
    4.获取用户信息
    5.根据用户信息进行判断
    6.登陆用户redis
        6.1 连接redis
        6.2 hash,set
        6.3 返回相应
    7.未登录用户cookie
        7.1 获取cart数据
        7.2 判断cart数据
        7.3 删除数据
        7.4 对字典进行加密处理
        7.5 返回相应

"""
########################@@@---登陸時合並購物車---@@@#############################
"""
    当用户登陆的时候,我们来合并 redis和cookie的数据

1.当用户登陆的时候


2.我们将cookie数据合并到redis中数据

    获取cookie数据  {sku_id:{count:xxx,selected:True}}

    获取redis数据   {sku_id:count}  [sku_id]

    合并

抽象问题  具体化

    cookie      {1:{count:10,selected:True},3:{count:10,selected:True}}

    # 我们为了减少客户端和服务器的交互 先把redis数据一次(2条指令)获取到
    redis       {2:20,3:20}   {3}

    合并
        以redis为主, redis的数据保证不怎么动
        如果cookie中有的数据,redis中没有,则可以添加
        如果cookie中有的数据,redis中有,则 数量以 cookie为主


    合并之后的:   {2:20,3:20}       []

                {2:20,3:10,1:10}       [1]


    再将最终的数据保存到redis中
                {2:20,3:10,1:10}       [1,3]

    合并之后cookie数据要删除

1.获取cookie信息
2.获取redis信息
3.做一个数据合并前的初始化(定义2个变量,一个是接收redis数据,另外一个是记录选中的id)
4.对cookie数据进行遍历,来合并到redis中
5.将合并之后的数据 保存到redis中
6.合并之后cookie数据要删除

##############################---邏輯實現---#############################

      cookie      {1:{count:10,selected:True},3:{count:10,selected:True}}

    # 我们为了减少客户端和服务器的交互 先把redis数据一次(2条指令)获取到
    redis       {b'2':b'20',b'3':b'20'}   {3}

    合并
        以redis为主, redis的数据保证不怎么动
        如果cookie中有的数据,redis中没有,则可以添加
        如果cookie中有的数据,redis中有,则 数量以 cookie为主


    合并之后的:   {2:20,3:20}       []

                {2:20,3:10,1:10}       [1]



    再将最终的数据保存到redis中
                {2:20,3:10,1:10}       [1,3]

    合并之后cookie数据要删除

1.获取cookie信息
2.获取redis信息
3.做一个数据合并前的初始化(定义2个变量,一个是接收redis数据,另外一个是记录选中的id)
4.对cookie数据进行遍历,来合并到redis中
5.将合并之后的数据 保存到redis中
6.合并之后cookie数据要删除
"""
##############################---管道---#############################
"""
    管道
    管道是基础Redis类的子类，它为在单个请求中向服务器缓冲多个命令提供支持。
    它们可用于通过减少客户端和服务器之间来回TCP数据包的数量来显着提高命令组的性能
    #A 创建管道
    pl = redis_conn.pipeline()
    #B 将redis指令添加到 管道中
    pl.hincrby('cart_%s'%user.id,sku_id,count)
    # set
    if selected:
        redis_conn.sadd('cart_selected_%s'%user.id,sku_id)
        pl.sadd('cart_selected_%s'%user.id,sku_id)

    #C,执行管道
    pl.execute()

"""
########################@@@---訂單業務邏輯模塊(order)---@@@#############################
"""
########################@---獲取購物車數據 業務邏輯---@@@#############################
    前端必须传递一个用户信息 token

    1. 这个视图必须是登陆用户才可以访问
    2. 获取用户信息
    3. 根据用户信息获取redis中选中的商品id  [1,2,3,4,5]
    4. 根据id查询商品信息  [sku,sku,sku]
    5. 将对象列表转换为字典列表

        #价格,最好使用 货币类型
        # from decimal import Decimal
        # # decimal 可以保存数据存储的精度
        # #  我们的小数在计算机中是以 无限接近于真实值的形式存在的
        #
        # # 100元 分3期  100/3=33.33     33.33+33.33+33.33=99.99
        # # 100元 分3期  100/3=33.33     33.33+33.33+33.34=100


        freight = Decimal('10')
        serializer = PlaceSerializer({
            'skus':skus,
            'freight':freight

        })

    6. 返回相应
    return Response(serializer.data)

########################@---提交訂單業務邏輯---@@@#############################


当用户提交订单按钮的时候,前端需要必须传递用户信息,地址信息,支付方式(订单id信息,价格不需要传递过来)

后端:
    1,接收数据
    2.验证数据
    3.数据入库
    4.返回相应

    POST        /order/


########################@---重寫 creat 方法---@@@#############################

前端只是将 用户的token, address,pay方式提交给了我们
我们需要 实现 订单信息以及订单商品列表数据的入库

一,先实现 订单信息的保存
    1.1 先接收地址信息
    1.2 接收支付方式
    1.3 接收用户信息
    1.4 生成订单id
    1.5 订单总数量和总价格(先为0)以及运费
    1.6 根据支付方式决定状态
    order
二,再去实现 订单商品列表的保存
    2.1 连接redis
    2.2 获取redis的数据 hash,set
    2.3 对redis数据进行转换,
            在转换过程中只转换 选中的信息
        {sku_id:count}
    2.4 对选中商品信息的列表进行遍历
        2.5 根据商品id进行查询
        2.6 查询出来之后我们要根据商品的数量进行
            判断,库存是否充足
        2.7 库存减少,销量增加
        2.8 遍历的过程中 累加商品数量和总价格
        2.9 商品数据入库

"""
########################@@@---数据库事务 mysql主從配置---@@@#############################
"""
########################@---数据库事务---@@@#############################
在保存订单数据中，涉及到多张表（OrderInfo、OrderGoods、SKU）的数据修改，
对这些数据的修改应该是一个整体事务，即要么一起成功，要么一起失败。
Django中对于数据库的事务，默认每执行一句数据库操作，便会自动提交。
我们需要在保存订单中自己控制数据库事务的执行流程。

------------------------------并发处理 :---------------------
在多个用户同时发起对同一个商品的下单请求时，先查询商品库存，再修改商品库存，会出现资源竞争问题，导致库存的最终结果出现异常

------------------------------悲观锁 :---------------------
    当查询某条记录时，即让数据库为该记录加锁，锁住记录后别人无法操作

------------------------------乐观锁:---------------------

    乐观锁并不是真实存在的锁，而是在更新的时候判断此时的库存是否是之前查询出的库存，
    如果相同，表示没人修改，可以更新库存，否则表示别人抢过资源，不再执行库存更新。

    肉包子:  25个肉包子
     甲: 25个   现在再看25个,甲就吃10个, 15个
     乙: 25个   再看的时候就是 15个了,不更新了

------------------------------事务隔离级别:---------------------
MySQL数据库事务隔离级别主要有四种：

Serializable 串行化，一个事务一个事务的执行
Repeatable read 可重复读，无论其他事务是否修改并提交了数据，在这个事务中看到的数据值始终不受其他事务影响

Read committed 读取已提交，其他事务提交了对数据的修改后，本事务就能读取到修改后的数据值
Read uncommitted 读取未提交，其他事务只要修改了数据，即使未提交，本事务也能看到修改后的数据值

修改方法：

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf

在 最後一行 添加
transaction-isolation=READ-COMMITTED

修改完成之后重启
sudo service mysql restart

------------------------------任务队列:---------------------
将下单的逻辑放到任务队列中（如celery），将并行转为串行，所有人排队下单。比如开启只有一个进程的Celery，一个订单一个订单的处理。

##########################################---mysql---############################
# 已經創建 mysql-slave  是下面注釋
# docker run --name mysql-slave -e MYSQL_ROOT_PASSWORD=mysql -d --network=host -v /home/python/Downloads/slave_38/data:/var/lib/mysql -v /home/python/Downloads/slave_38/mysql.conf.d:/etc/mysql/mysql.conf.d  mysql:5.7.22

# docker container start mysql-slave # 先開啓mysql-slave

-----------------------------主服務器------------------------------
mysql -uroot -pmysql

GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' identified by 'slave';

FLUSH PRIVILEGES;

SHOW MASTER STATUS; #主服務器執行 查看 File为使用的日志文件名字，Position为使用的文件位置，

# sudo service mysql restart

-----------------------------從服務器------------------------------
docker exec -it docker進程號 /bin/bash

mysql -uroot -pmysql -h 127.0.0.1 --port=8306   # 啓動 mysql-server

# 修改 file 和 Position

# change master to master_host='127.0.0.1', master_user='slave', master_password='slave',master_log_file='mysql-bin.000081', master_log_pos=1026;

# 启动slave服务器，并查看同步状态
start slave;
show slave status \G

Slave_IO_Running: Yes  表示同步成功
Slave_SQL_Running: Yes  表示同步成功

"""
########################@@@---支付寶功能(pay)---@@@#############################
########################---前期準備---#############################
"""
1.申請成爲開發者
2.沙箱環境
    沙箱应用：https://docs.open.alipay.com/200/105311
    沙箱账号：https://openhome.alipay.com/platform/appDaily.htm?tab=account
3.開發文檔 支付宝开发者文档
    文档主页：https://openhome.alipay.com/developmentDocument.htm
    产品介绍：https://docs.open.alipay.com/270
    快速接入：https://docs.open.alipay.com/270/105899/
    SDK：https://docs.open.alipay.com/270/106291/
    python对接支付宝SDK：https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
    python对接支付宝SDK安装：pip install python-alipay-sdk --upgrade
    API列表：https://docs.open.alipay.com/270/105900/

4.配置秘钥
    1. 生成应用的私钥和公钥
    openssl
    OpenSSL> genrsa -out app_private_key.pem 2048  # 私钥RSA2
    OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
    OpenSSL> exit
    2. 保存应用私钥文件
    在pay应用中新建keys目录，用来保存秘钥文件。
    将应用私钥文件app_private_key.pem复制到pay/keys目录下。
    3. 查看公钥
    cat app_public_key.pem
    将公钥内容复制给支付宝
    4. 保存支付宝公钥
    在pay/keys目录下新建alipay_public_key.pem文件，用于保存支付宝的公钥文件。
    将支付宝的公钥内容复制到alipay_public_key.pem文件中

    注意，还需要在公钥文件中补充开始与结束标志
    -----BEGIN PUBLIC KEY-----
    此处是公钥内容
    -----END PUBLIC KEY-----

    5.安裝 Alipay支付API
        python对接支付宝SDK安装：pip install python-alipay-sdk --upgrade
    6.後端接口設計
    请求方式： GET/orders/(?P<order_id>\d+)/payment/

    7.沙箱环境接口联调

    在沙箱支持产品列表中的所有产品接口均已开通权限，开发者只需配置以下信息即可联调接口：

    1. 沙箱appid
    2. 沙箱网关
    我们需要组织2套公钥和私钥
        一套是支付宝的,另外一个是我们自己应用程序的
        3. 沙箱应用私钥(我们自己的)
            私钥:放在我们的程序中
            公钥:放在支付宝的网站上
        4. 沙箱支付宝公钥(支付宝)
             私钥:放在支付宝的服务器上
             公钥:放在支付宝的网站上,我们需要拷贝下来,放在我们的程序中
"""
########################---邏輯實現--#############################
"""

当登陆用户点击去支付的时候,需要让前端将订单id传递给后端

1.后端接收订单id
2.根据订单id进行订单信息的查询
3.创建支付宝实例对象
4.生成order_string
5.拼接支付的url

get   /orders/(?P<order_id>\d+)/payment/

支付信息

    我们除了要保存支付宝的交易流水号之外,还需要保存交易流水号所对应的订单信息

当支付成功之后,需要让前端将回调的参数以查询字符串的形式返回回来

# 1.接收查询字符串参数
# 2.根据支付宝文档进行数据的验证
# 3. 如果验证成功则获取 支付宝流水号和订单id
# 4.  将支付宝流水号和订单id 保存起来
# 5. 更新订单状态

PUT     pay/status/?xxx=xxx

"""
########################@---XAdmin---@@@#############################
"""
1. 安装

    通过如下命令安装xadmin的最新版
        pip install https://github.com/sshwsfc/xadmin/tarball/master
    在配置文件中注册如下应用

    INSTALLED_APPS = [
        ...
        'xadmin',
        'crispy_forms',
        'reversion',
        ...
    ]

    安裝過程中可能 缺包  xadmin
    pip install django>=1.9.0
    pip install django-import-export>=0.5.1
    pip install django-crispy-forms>=1.6.0
    pip install django-reversion>=2.0.0
    pip install future==0.15.2
    pip install httplib2==0.9.2
    pip install six==1.10.0

    pip install django-formtools==2.1

    xadmin有建立自己的数据库模型类，需要进行数据库迁移
    python manage.py makemigrations
    python manage.py migrate

    在总路由中添加xadmin的路由信息

    import xadmin

    urlpatterns = [
        # url(r'^admin/', admin.site.urls),
        url(r'xadmin/', include(xadmin.site.urls)),
        ...
    ]

2. 使用
    xadmin不再使用Django的admin.py，而是需要编写代码在adminx.py文件中。
    xadmin的站点管理类不用继承admin.ModelAdmin，而是直接继承object即可。
    在goods应用中创建adminx.py文件。

    站点的全局配置


"""
########################@---讀寫分離---@@@#############################
"""
1. 在配置文件中增加slave数据库的配置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': 'mysql',  # 数据库用户密码
        'NAME': 'meiduo'  # 数据库名字
    },
    'slave': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 8306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': 'mysql',  # 数据库用户密码
        'NAME': 'meiduo'  # 数据库名字
    }
}

2. 创建数据库操作的路由分发类
在mall/utils中创建db_router.py

class MasterSlaveRouter(object):

    def db_for_read(self, model, **hints):

        return "slave"

    def db_for_write(self, model, **hints):

        return "default"

    def allow_relation(self, obj1, obj2, **hints):

        return True

3. 配置读写分离路由
在配置文件中增加

# 配置读写分离
DATABASE_ROUTERS = ['utils.db_router.MasterSlaveRouter'
"""
########################@---nginx---@@@#############################
"""
Nginx特点(優點):
    高并发量：支持高并发量，高达 5w 并发连接数的响应
    内存消耗少：善于处理静态文件，相较于其他web(比如：apache),占用更少的内存及资源
    简单稳定：配置简单(一个conf文件),运行简单(nginx命令)，而且运行稳定
    模块化程度高：功能模块插件化设计，可以自由配置相应的功能。
    支持Rwrite重写规则：能够根据域名、URL等请求关键点，实现定制化的高质量分发。
    低成本：Nginx的负载均衡功能很强大而且免费开源，相较于几十万的硬件负载均衡器成本相当低。
    支持多系统：Nginx代码完全用C语言从头写成，可以在各系统上编译并使用。
缺點：动态文件处理能力差
安裝: sudoapt-get install -y build-essential libssl-dev libtool libpcre3 libpcre3-dev make openssl zlib1g-dev
開啓: systemctl start nginx
停止: systemctl stop nginx
重啓: systemctl reload nginx

cd /etc/nginx
sudo gedit nginx.conf  #編輯 nginx 配置
sudo nginx -t    #檢查指令是否正確
sudo vi /etc/hosts   #添加 域名

ps aux|grep nginx   查看 nginx 運行狀態


安装好的文件位置：
/usr/sbin/nginx：主程序
/etc/nginx：存放配置文件
/usr/share/nginx：存放静态文件
/var/log/nginx：存放日志

移除相关命令：
查看和nginx相关软件
dpkg --get-selections|grep nginx

移除nginx，包括相关文件
sudoapt-get --purge remove nginx
sudo apt-get --purge remove nginx-common
sudo apt-get --purge remove nginx-core


nginx 代理 总结：

1、正向代理，我们不对外提供服务，反而是对外消费服务，属于消费者  我们的角色是 被代理者

2、反向代理，我们对外提供服务，属于服务提供者  我们的角色是 局域网 web服务

########################@---反向代理配置---@@@#############################

在/etc/nginx/sites-enabled目录下创建目录下proxy配置文件

server {
    #监听的端口号
    listen 8001;
    #服务名
    server_name 192.168.229.128;
    location / {
        #指向代理
        proxy_pass http://192.168.229.128:8000/;
    }

}

########################@---负载均衡配置文件---@@@#############################

在/etc/nginx/sites-enabled目录下创建目录下upstream配置文件

#负载均衡
upstream backends {
    server 192.168.229.128:10086;
    server 192.168.229.128:10087;
    server 192.168.229.128:10088;
}

server {
    #监听的端口号
    listen 9001;
    #服务器
    server_name 192.168.229.128;
    location / {
        #指向代理
        proxy_pass http://backends/;
    }

}

Nginx提供的负载均衡策略有两种: nginx自带的算法

雨露均沾型：轮训、加权轮训、哈希
定向服务型：ip_hash、least_conn、cookie、route、lean、
商业类型：ntlm、least_time、queue、stick

加权      修改负载均衡配置文件

#负载均衡
upstream backends {
    server 192.168.229.128:10086 backup;    #后备主机，当所有非backup机器出现故障或者繁忙的时候，才会请求backup机器
    server 192.168.229.128:10087 weight=1;  #指定轮询权重，值越大，分配到的几率就越高，适用于后端服务器性能不均衡情况
    server 192.168.229.128:10088 weight=2;
}
server {
    #监听的端口号
    listen 9001;
    #服务器
    server_name 192.168.229.128;
    location / {
        #指向代理
        proxy_pass http://backends/;
    }
}

"""
################@---nginx-->uswsgi-->django---@@@######################
"""
Nginx 服务器 主要是接收请求

请求分为2部分:
静态请求:   css/js/image/index.html/detail.html/login.html

            www.meiduo.site:80

动态请求(需要django进行业务逻辑处理,例如登陆):   ajax的请求,

            api.meiduo.site:8000


          Nginx 只要用于处理静态请求,动态请求的化需要

        Nginx --socket-->转发给uwsig -->uwsgi再转发给django


        测试:  uswsgi和django能否进行通讯

                http-->uwsgi -->django
"""
################@---常用命令---@@@######################
"""
##########################################---gitee---############################

https://gitee.com/itcastitheima/meiduo_38
https://gitee.com/xszyh/meiduo_38.git

##########################################---修改 ip 地址---############################
#修改 ip 地址： sudo ifconfig ens33 192.168.31.142


###################---live-server/http-server/-->簡寫爲-->hss/hs---############################

cd /Desktop/py38_Django/meiduo_38/front/

##########################################---搜索---############################
python manage.py rebuild_index

##########################################---異步任務---############################
#需要在虚拟环境中执行
# celery -A celery对象的路径 worker -l info
# celery -A celery_tasks.main worker -l info

##########################################---定時任務---############################
添加定时任务到系统中
python manage.py crontab add

显示已经激活的定时任务
python manage.py crontab show

移除定时任务
python manage.py crontab remove

##########################################---數據表遷移---############################

python manage.py makemigrations
python manage.py migrate

##########################################---nginx---############################
cd /etc/nginx
sudo gedit nginx.conf  #編輯 nginx 配置
sudo nginx -t    #檢查指令是否正確
sudo vi /etc/hosts   #添加 域名

ps aux|grep nginx   查看 nginx 運行狀態

開啓: systemctl start nginx
重啓: systemctl reload nginx
停止: systemctl stop nginx

安装好的文件位置：
/usr/sbin/nginx：主程序
/etc/nginx：存放配置文件
/usr/share/nginx：存放静态文件
/var/log/nginx：存放日志
##########################################---uwsgi---############################

ps aux|grep uwsgi #查看 uwsgi

uwsgi --ini uwsgi.ini  #啓動 uwsgi

uwsgi --stop uwsgi.pid   #停止 uwsgi

##########################################---docker---############################
docker load -i 壓縮包  安裝 壓縮鏡像

docker commit -m 'meiduo' myubuntu myubuntu:v1.2   創建新的鏡像+動作
docker commit  myubuntu myubuntu:v1.3   創建新的鏡像

docker build -t nginx:v2 . 當前路徑創建鏡像

docker run -dit -p 192.168.31.142:10086:80 nginx:v2  國定ip及端口

curl ip  顯示 內容

docker rmi [image_id/image_name:image_version]  刪除鏡像

docker ps -a  顯示所有的進程
docker inspect [容器id] 詳細信息
docker run --name container myubuntu ubuntu    重命名
docker exec -it docker容器號 /bin/bash   進入到容器
docker run -dit --name myubuntu -v /home/python/Desktop/38_01 ubuntu
docker create -v /home/python/Desktop/abc --name vc-share ubuntu 創建共享目錄地址
docker run --volumes-from vc-share -dit --name test1 ubuntu
docker run --volumes-from vc-share -dit --name test2 ubuntu



开启 tracker 服務

docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker

开启 storage 服務

docker run -dti --network=host --name storage -e TRACKER_SERVER=192.168.31.142:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage

停止/開啓 storage 服務

docker container stop/start storage

docker container rm 容器名或容器id  # 刪除 容器
docker  rm  -f 容器名或容器id  # 刪除 容器

创建 elasticsearch 容器

docker run -dti --network=host --name=elasticsearch -v /home/python/Downloads/docker/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

docker container start tracker
docker container start storage
docker container start elasticsearch

##########################################---mysql---############################

# 已經創建 mysql-slave  是下面注釋
# docker run --name mysql-slave -e MYSQL_ROOT_PASSWORD=mysql -d --network=host -v /home/python/Downloads/slave_38/data:/var/lib/mysql -v /home/python/Downloads/slave_38/mysql.conf.d:/etc/mysql/mysql.conf.d  mysql:5.7.22

# docker container start mysql-slave # 先開啓mysql-slave

-----------------------------主服務器------------------------------
mysql -uroot -pmysql

GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' identified by 'slave';

FLUSH PRIVILEGES;

SHOW MASTER STATUS; #主服務器執行 查看 File为使用的日志文件名字，Position为使用的文件位置，

# sudo service mysql restart

-----------------------------從服務器------------------------------
docker exec -it docker進程號 /bin/bash

mysql -uroot -pmysql -h 127.0.0.1 --port=8306   # 啓動 mysql-server

# 修改 file 和 Position

# change master to master_host='127.0.0.1', master_user='slave', master_password='slave',master_log_file='mysql-bin.000081', master_log_pos=1026;

# 启动slave服务器，并查看同步状态
start slave;
show slave status \G

Slave_IO_Running: Yes  表示同步成功
Slave_SQL_Running: Yes  表示同步成功



-----------------------------支付寶------------------------------
买家账号axirmj7487@sandbox.com
登录密码111111
支付密码111111

买家账号fyuflc3396@sandbox.com
登录密码111111
支付密码111111
"""